﻿/*
   Open Intel 
   Copyright © 2011 – ISC. All Rights Reserved.
*/

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization.Formatters;
using System.Text.RegularExpressions;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;


namespace OI.DataModel
{
    public static class MessageSerializer
    {
        private static readonly MessageBinder _Binder = new MessageBinder();

        private static readonly JsonSerializer _Serializer = new JsonSerializer
        {
            NullValueHandling = NullValueHandling.Ignore,
            TypeNameHandling = TypeNameHandling.All,
            TypeNameAssemblyFormat = FormatterAssemblyStyle.Simple,
            Binder = _Binder
        };

        public static void Serialize(object request, Stream stream)
        {
            using (var writer = new StreamWriter(stream))
            using (var jsonWriter = new JsonTextWriter(writer))
            {
                _Serializer.Serialize(jsonWriter, request);
            }
        }

        public static string Serialize(object request)
        {
            using (var writer = new StringWriter())
            using (var jsonWriter = new JsonTextWriter(writer))
            {
                _Serializer.Serialize(jsonWriter, request);
                return writer.ToString();
            }
        }

        public static object Deserialize(Stream stream)
        {
            using (var reader = new StreamReader(stream))
            using (var jsonReader = new JsonTextReader(reader))
            {
                object result = _Serializer.Deserialize(jsonReader);
                return result;
            }
        }

        public static object Deserialize(string json)
        {
            using (var reader = new StringReader(json))
            using (var jsonReader = new JsonTextReader(reader))
            {
                object result = _Serializer.Deserialize(jsonReader);
                return result;
            }
        }

        public static T Deserialize<T>(string json)
        {
            using (var reader = new StringReader(json))
            using (var jsonReader = new JsonTextReader(reader))
            {
                object result = _Serializer.Deserialize(jsonReader);
                return (T) result;
            }
        }
    }


    public class MessageBinder : DefaultSerializationBinder
    {
        private readonly Assembly[] Assemblies;
        private readonly Dictionary<string, Type> Cache = new Dictionary<string, Type>();
        private readonly Dictionary<string, Func<string, Type>> TypeMap;

        public MessageBinder()
        {
            Assemblies = new[]
            {
                Assembly.GetExecutingAssembly()
            };

            TypeMap = new Dictionary<string, Func<string, Type>>
            {
                {"System.Collections.Generic.Dictionary`2[", BuildDictionaryType},
                {"System.Collections.Generic.List`1[", BuildListType},
            };

            IEnumerable<Type> types = from assembly in Assemblies
                                      from type in assembly.GetExportedTypes()
                                      where type != null
                                      select type;

            Cache = types.ToDictionary(x => x.FullName, x => x);
        }


        public override Type BindToType(string assemblyName, string typeName)
        {
            var t = LookupType(typeName);
            return t ?? base.BindToType(assemblyName, typeName);
        }


        private Type IsCustomOverrideTypes(string typeName)
        {
            Type t = null;
            var p = TypeMap.Where(pair => typeName.StartsWith(pair.Key)).FirstOrDefault();
            if (p.Key != null)
            {
                t = p.Value(typeName);
            }
            return t;
        }


        private Type BuildListType(string typeName)
        {
            var re = new Regex(@"\[(?<key>[\w,\d\.]+),\s[\w,\d\.]+\]");
            GroupCollection groups = re.Match(typeName).Groups;
            Type key = GetTypeFor("key", groups);
            var t = typeof (List<>).MakeGenericType(key);
            return t;
        }


        private Type BuildDictionaryType(string typeName)
        {
            var re = new Regex(@"\[(?<key>[\w,\d\.]+),\s[\w,\d\.]+\],\[(?<value>[\w,\d\.]+),");
            GroupCollection groups = re.Match(typeName).Groups;
            Type key = GetTypeFor("key", groups);
            Type value = GetTypeFor("value", groups);
            return typeof(Dictionary<,>).MakeGenericType(key, value);
        }


        private Type GetTypeFor(string id, GroupCollection groups)
        {
            string typeStr = groups[id].Success ? groups[id].Value : string.Empty;
            return LookupType(typeStr);
        }


        private Type FindTypeMatchingInAssemblyList(string typeName)
        {
            var t = Assemblies.Select(assembly => 
                assembly.GetType(typeName, false)).
                FirstOrDefault(type => type != null);

            return t;
        }


        private Type LookupType(string typeName)
        {
            if (Cache.ContainsKey(typeName))
            {
                return Cache[typeName];
            }

            Type t = IsCustomOverrideTypes(typeName);
            if (t == null)
            {
                t = Type.GetType(typeName, false);
                if (t == null)
                {
                    t = FindTypeMatchingInAssemblyList(typeName);
                    if (t == null)
                    {
                        return null;
                    }
                }
            }
            Cache[typeName] = t;
            return t;
        }
    }
}